package com.rmd.ai.grpc.test;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;

import javax.sound.sampled.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.util.Arrays;

@Slf4j
public class AudioUtil {
    public static final int SAMPLE_RATE = 16000;

    // 16-bit audio
    private static final int BYTES_PER_SAMPLE = 2;
    // 16-bit audio
    private static final int BITS_PER_SAMPLE = 16;
    private static final double MAX_16_BIT = 32768;
    private static final int SAMPLE_BUFFER_SIZE = 4096;

    private static final int MONO = 1;
    private static final int STEREO = 2;
    private static final boolean LITTLE_ENDIAN = false;
    private static final boolean BIG_ENDIAN = true;
    private static final boolean SIGNED = true;
    private static final boolean UNSIGNED = false;


    public static byte[] reSamplingToULaw(int fs, byte[] data) {

        File tempFile = null;
        AudioInputStream audioIn = null;
        try {
            //FileUtils.writeByteArrayToFile(new File("/home/leo/下载/"+new Random().nextInt(100000)+".pcm"),data);
            audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, audioIn);
            if (fs != 16000) {
                AudioFormat reSampleFormat = new AudioFormat(fs, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }
            AudioInputStream ULaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ULAW, pcm);

            tempFile = File.createTempFile("wav", "tmp");
            AudioSystem.write(ULaw, AudioFileFormat.Type.WAVE, tempFile);
            return FileUtils.readFileToByteArray(tempFile);
        }catch (IOException | UnsupportedAudioFileException e){
            log.error("",e);
        }finally {
            if (tempFile != null){
                tempFile.delete();
            }if (audioIn != null){
                try {
                    audioIn.close();
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
        return data;
    }

    public static byte[] reSamplingToAlaw(int fs, byte[] data) {

        File tempFile = null;
        AudioInputStream audioIn = null;
        try {
            //FileUtils.writeByteArrayToFile(new File("/home/leo/下载/"+new Random().nextInt(100000)+".pcm"),data);
            audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, audioIn);
            if (fs != 16000) {
                AudioFormat reSampleFormat = new AudioFormat(fs, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }
            AudioInputStream alaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ALAW, pcm);

            tempFile = File.createTempFile("wav", "tmp");
            AudioSystem.write(alaw, AudioFileFormat.Type.WAVE, tempFile);
            return FileUtils.readFileToByteArray(tempFile);
        }catch (IOException | UnsupportedAudioFileException e){
            log.error("",e);
        }finally {
            if (tempFile != null){
                tempFile.delete();
            }if (audioIn != null){
                try {
                    audioIn.close();
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
        return data;
    }
    public static byte[] reSamplingToPcm(int fs, byte[] data) {

        File tempFile = null;
        AudioInputStream audioIn = null;
        try {
            //FileUtils.writeByteArrayToFile(new File("/home/leo/下载/"+new Random().nextInt(100000)+".pcm"),data);
            audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, audioIn);
            if (fs != 16000) {
                AudioFormat reSampleFormat = new AudioFormat(fs, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }

            tempFile = File.createTempFile("wav", "tmp");
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, tempFile);
            return FileUtils.readFileToByteArray(tempFile);
        }catch (IOException | UnsupportedAudioFileException e){
            log.error("",e);
        }finally {
            if (tempFile != null){
                tempFile.delete();
            }if (audioIn != null){
                try {
                    audioIn.close();
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
        return data;
    }
    public static byte[] reSampling8KPcmToULaw(int fs, byte[] data) {

        File tempFile = null;
        AudioInputStream audioIn = null;
        try {
            //FileUtils.writeByteArrayToFile(new File("/home/leo/下载/"+new Random().nextInt(100000)+".pcm"),data);
            audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, audioIn);
            if (fs != 16000) {
                AudioFormat reSampleFormat = new AudioFormat(fs, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }
            AudioInputStream ULaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ULAW, pcm);

            tempFile = File.createTempFile("wav", "tmp");
            AudioSystem.write(ULaw, AudioFileFormat.Type.WAVE, tempFile);
            return FileUtils.readFileToByteArray(tempFile);
        }catch (IOException | UnsupportedAudioFileException e){
            log.error("",e);
        }finally {
            if (tempFile != null){
                tempFile.delete();
            }if (audioIn != null){
                try {
                    audioIn.close();
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
        return data;
    }
    public static byte[] convertULawToPCM(int fs, byte[] data) {
        try {
            long fileSize = data.length;
            int frameSize = 1;
            long numFrames = fileSize / frameSize;
            AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.ULAW, fs, 8, 1, frameSize, 8000, false);
            AudioInputStream ULaw = new AudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)), audioFormat, numFrames);
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, ULaw);

            /*if (fs != 16000) {
                AudioFormat reSampleFormat = new AudioFormat(16000, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }

             */
            /*
            File tempFile = File.createTempFile("wav","tmp");
            //ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, tempFile);
            byte[] bytes = FileUtils.readFileToByteArray(tempFile);
             */

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, outputStream);
            byte[] bytes = outputStream.toByteArray();
            outputStream.close();
            if (bytes.length > 44){
                byte[] newData = new byte[bytes.length - 44];
                System.arraycopy(bytes,44,newData,0,newData.length);
                return newData;
            }
            return bytes;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

        }
        return data;
    }
    public static byte[] convertALawToPCM(int fs, byte[] data) {
        try {
            long fileSize = data.length;
            int frameSize = 1;
            long numFrames = fileSize / frameSize;
            AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.ALAW, fs, 8, 1, frameSize, 8000, false);
            AudioInputStream alaw = new AudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)), audioFormat, numFrames);
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, alaw);

            /*if (fs != 16000) {
                AudioFormat reSampleFormat = new AudioFormat(16000, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }

             */
            /*
            File tempFile = File.createTempFile("wav","tmp");
            //ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, tempFile);
            byte[] bytes = FileUtils.readFileToByteArray(tempFile);
             */

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, outputStream);
            byte[] bytes = outputStream.toByteArray();
            outputStream.close();
            if (bytes.length > 44){
                byte[] newData = new byte[bytes.length - 44];
                System.arraycopy(bytes,44,newData,0,newData.length);
                return newData;
            }
            return bytes;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

        }
        return data;
    }
    public static byte[] reSamplingULawToPcm(int fs, byte[] data) {
        File tempFile = null;
        AudioInputStream audioIn = null;
        try {
            audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)));
            AudioInputStream ULaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ULAW, audioIn);
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, ULaw);
            if (fs != 8000) {
                AudioFormat reSampleFormat = new AudioFormat(fs, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }


            tempFile = File.createTempFile("wav", "tmp");
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, tempFile);
            return FileUtils.readFileToByteArray(tempFile);
        }catch (IOException | UnsupportedAudioFileException e){
            log.error("",e);
        } finally {
            if (tempFile != null){
                tempFile.delete();
            }if (audioIn != null){
                try {
                    audioIn.close();
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
        return data;
    }
    public static byte[] reSamplingAlawToPcm(int fs, byte[] data) {
        File tempFile = null;
        AudioInputStream audioIn = null;
        try {
            audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)));
            AudioInputStream alaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ALAW, audioIn);
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, alaw);
            if (fs != 8000) {
                AudioFormat reSampleFormat = new AudioFormat(fs, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
                pcm = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            }


            tempFile = File.createTempFile("wav", "tmp");
            AudioSystem.write(pcm, AudioFileFormat.Type.WAVE, tempFile);
            return FileUtils.readFileToByteArray(tempFile);
        }catch (IOException | UnsupportedAudioFileException e){
            log.error("",e);
        } finally {
            if (tempFile != null){
                tempFile.delete();
            }if (audioIn != null){
                try {
                    audioIn.close();
                } catch (IOException e) {
                    log.error("",e);
                }
            }
        }
        return data;
    }

    public static short[] bytesToShortLittleEndian(byte[] bytes) {
        if(bytes==null){
            return null;
        }
        short[] shorts = new short[bytes.length/2];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
        return shorts;
    }
    public static byte[] shortToBytesLittleEndian(short[] shorts) {
        if(shorts==null){
            return null;
        }
        byte[] bytes = new byte[shorts.length * 2];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shorts);

        return bytes;
    }


    public static void wrapperULawHeaderAndStoreFile(byte[] data, String fileName){
        try {
            long fileSize = data.length;
            int frameSize = 1;
            long numFrames = fileSize / frameSize;
            AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.ULAW, 8000, 8, 1, frameSize, 8000, false);
            AudioInputStream ULaw = new AudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)), audioFormat, numFrames);

            //ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            AudioSystem.write(ULaw, AudioFileFormat.Type.WAVE, new File(fileName));

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public static void wrapperAlawHeaderAndStoreFile(byte[] data, String fileName){
        try {
            long fileSize = data.length;
            int frameSize = 1;
            long numFrames = fileSize / frameSize;
            AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.ALAW, 8000, 8, 1, frameSize, 8000, false);
            AudioInputStream alaw = new AudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)), audioFormat, numFrames);

            //ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            AudioSystem.write(alaw, AudioFileFormat.Type.WAVE, new File(fileName));

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void pcmToULawFile(int fs, byte[] data, String fileName){
        AudioInputStream source = null;
        try {
            source = AudioSystem.getAudioInputStream(new ByteArrayInputStream(data));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, source);
            AudioFormat reSampleFormat = new AudioFormat(fs, source.getFormat().getSampleSizeInBits(), source.getFormat().getChannels(), true, source.getFormat().isBigEndian());
            AudioInputStream reSampleStream = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            AudioInputStream ULaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ULAW, reSampleStream);
            AudioSystem.write(ULaw, AudioFileFormat.Type.WAVE,new File(fileName));
        }catch (Exception e){
            log.error("PcmToULawFile Error",e);

            if (source != null) {
                try {
                    source.close();
                } catch (IOException e1) {
                    log.error("",e);
                }
            }
        }
    }
    public static void pcmToAlawFile(int fs, byte[] data, String fileName){
        AudioInputStream source = null;
        try {
            source = AudioSystem.getAudioInputStream(new ByteArrayInputStream(data));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, source);
            AudioFormat reSampleFormat = new AudioFormat(fs, source.getFormat().getSampleSizeInBits(), source.getFormat().getChannels(), true, source.getFormat().isBigEndian());
            AudioInputStream reSampleStream = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            AudioInputStream alaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ALAW, reSampleStream);
            AudioSystem.write(alaw, AudioFileFormat.Type.WAVE,new File(fileName));
        }catch (Exception e){
            log.error("PcmToULawFile Error",e);

            if (source != null) {
                try {
                    source.close();
                } catch (IOException e1) {
                    log.error("",e);
                }
            }
        }
    }


    public static void pcm16KWrapperHeaderToULawFile(byte[] data, String fileName){
        AudioInputStream source = null;
        try {
            long fileSize = data.length;
            int frameSize = 2;
            long numFrames = fileSize / frameSize;
            AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 1, frameSize, 16000, false);
            AudioInputStream pcm = new AudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)), audioFormat, numFrames);

            AudioFormat reSampleFormat = new AudioFormat(8000, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
            AudioInputStream reSampleStream = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            AudioInputStream ULaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ULAW, reSampleStream);
            AudioSystem.write(ULaw, AudioFileFormat.Type.WAVE,new File(fileName));
        }catch (Exception e){
            log.error("PcmToULawFile Error",e);

            if (source != null) {
                try {
                    source.close();
                } catch (IOException e1) {
                    log.error("",e);
                }
            }
        }
    }

    public static void pcm16KWrapperHeaderToAlawFile(byte[] data, String fileName){
        AudioInputStream source = null;
        try {
            long fileSize = data.length;
            int frameSize = 2;
            long numFrames = fileSize / frameSize;
            AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 16000, 16, 1, frameSize, 16000, false);
            AudioInputStream pcm = new AudioInputStream(new BufferedInputStream(new ByteArrayInputStream(data)), audioFormat, numFrames);

            AudioFormat reSampleFormat = new AudioFormat(8000, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
            AudioInputStream reSampleStream = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            AudioInputStream alaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ALAW, reSampleStream);
            AudioSystem.write(alaw, AudioFileFormat.Type.WAVE,new File(fileName));
        }catch (Exception e){
            log.error("PcmToULawFile Error",e);

            if (source != null) {
                try {
                    source.close();
                } catch (IOException e1) {
                    log.error("",e);
                }
            }
        }
    }

    public static byte[] reSamplingPCMByJave(byte[] data) {

        try {
            AudioInputStream audioIn = AudioSystem.getAudioInputStream(new BufferedInputStream(Files.newInputStream(new File("/home/leo/下载/ori.wav").toPath())));
            AudioInputStream pcm = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, audioIn);
            AudioFormat reSampleFormat = new AudioFormat(8000, pcm.getFormat().getSampleSizeInBits(), pcm.getFormat().getChannels(), true, pcm.getFormat().isBigEndian());
            AudioInputStream reSampleStream = AudioSystem.getAudioInputStream(reSampleFormat, pcm);
            AudioInputStream ULaw = AudioSystem.getAudioInputStream(AudioFormat.Encoding.ULAW, reSampleStream);

            File tempFile = new File("/home/leo/下载/ULaw.wav");
            AudioSystem.write(ULaw, AudioFileFormat.Type.WAVE, tempFile);
        }catch (Exception e){
            log.error("",e);
        }
        return data;
    }

    /**
     * 去除wave头信息44个字节，电话播放有爆音可能是因为有个头信息
     * 模拟电话有吃音问题，在欢迎语前加1秒的空白
     * @param waveData
     * @return
     */
    public static byte[] wipeOffWaveHeader(byte[] waveData, boolean blankHeader){
        byte[] data = new byte[0];

        if (waveData.length > 46) {
            data = new byte[waveData.length - 46];
            System.arraycopy(waveData, 46, data, 0, data.length);

        }


        //模拟电话有吃音问题，在欢迎语前加1秒的空白
        if (blankHeader){
            byte[] playData = new byte[160 * 50];
            Arrays.fill(playData, (byte) 0xff);
            playData = Arrays.copyOf(playData, playData.length + data.length);
            System.arraycopy(data, 0, playData, 160 * 50, data.length);
            data = playData;
        }
        return data;
    }
    public static int calcDecibelLevel(short[] buffer, int readSize){
        long v = 0;
        for (int i=0;i<buffer.length;i++){
            v += buffer[i] * buffer[i];
        }
        double mean = v/(double) readSize;
        double volume = 10 * Math.log10(mean);
        return (int)volume;
    }
    public static int getVolumeLevel(int fs, byte[] samples){
        short[] audioData = AudioUtil.bytesToShortLittleEndian(samples);
        int maxLevel = 0;
        int binSize = 160 * (fs / 8000);
        short[] audioSample = new short[binSize];
        int binIdx = 0;
        for(int i = 0; i < audioData.length; i++) {
            short currentSample = audioData[i];
            binIdx = i % binSize;

            //we have filled a bin, let's see if there's speech in it
            if (binIdx == binSize - 1) {
                try {
                    int nn = AudioUtil.calcDecibelLevel(audioSample, 160);
                    maxLevel = Math.max(nn,maxLevel);
                } catch (Exception e) {
                    log.error("",e);
                }
            }
            audioSample[binIdx] = currentSample;
        }
        return maxLevel;
    }
    public static boolean checkTotalSpeechVolume(int fs, byte[] samples, int asrSilentVolume){
        int maxSize = 0;
        int binSize = 320 * (fs / 8000);
        byte[] audioSample = new byte[binSize];
        int binIdx = 0;
        for(int i = 0; i < samples.length; i++) {
            byte currentSample = samples[i];
            binIdx = i % binSize;

            //we have filled a bin, let's see if there's speech in it
            if (binIdx == binSize - 1) {
                int volume = getVolumeLevel(fs,audioSample);
                //log.info("volume:"+volume);
                if (volume > asrSilentVolume)
                    maxSize++;
            }
            audioSample[binIdx] = currentSample;
        }
        return maxSize > 5;
    }
    public static float[] bytesToFloatLittleEndian(byte[] bytes) {
        if(bytes==null){
            return null;
        }
        float[] floats = new float[bytes.length/4];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().get(floats);
        return floats;
    }
    public static byte[] floatToBytesLittleEndian(float[] floats) {
        if(floats==null){
            return null;
        }
        byte[] bytes = new byte[floats.length * 4];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(floats);

        return bytes;
    }

    public static byte[] unitePcmFile(byte[] userPcmWav,byte[] robotPcmWav) {
        int robotLength = robotPcmWav.length;
        byte[] out = new byte[userPcmWav.length];
        for (int i = 0; i < userPcmWav.length; i++)
            out[i] = (byte) ((i < robotLength ? userPcmWav[i] + robotPcmWav[i]: userPcmWav[i] ) >> 1);
        return out;
    }




}
